package com.fszs.framework.aspectj;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.fszs.common.enums.HttpMethod;
import com.fszs.common.utils.SecurityUtils;
import com.fszs.common.utils.ServletUtils;
import com.fszs.common.utils.StringUtils;
import com.fszs.framework.security.LoginUser;
import com.fszs.framework.web.domain.AjaxResult;
import com.fszs.framework.web.page.TableDataInfo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * 日志打印相关切面配置
 */
@Component
@Aspect
public class LogPrintAspect {
    private static final Logger log = LoggerFactory.getLogger(LogPrintAspect.class);

    /**
     * 计时器标识
     */
    private static final String TIMER_START = "TIMER_START";
    @Value("${spring.application.name:fszs}")
    String serviceName;


    @Pointcut("execution(* com..controller..*.*(..)) && !within(com.fszs.framework.web.controller.BaseController)")
    public void controllerPointCut() {
    }


    @Before("controllerPointCut()")
    public void baseBefore(JoinPoint point) {
        HttpServletRequest request = getRequest();
        // 设置定时器开始标志
        this.setTimerStart(request);
        System.out.println();
    }

    private void setTimerStart(HttpServletRequest request) {
        // debug 级别下 || 开启配置时记录开始时间
        if (log.isInfoEnabled()) {
            String reqUri = getRequestURI(request);
            log.info("接收到请求: {}", reqUri);
            try {
                request.setAttribute(TIMER_START, System.currentTimeMillis());
            } catch (Exception e) {
                log.error("设置TIMER_START失败,不影响正常业务流程", e);
            }
        }
    }

    public String getRequestURI(HttpServletRequest request) {
        String attrName = "RequestURI";
        String uri = (String) request.getAttribute(attrName);
        if (StrUtil.isNotEmpty(uri)) {
            return uri;
        }
        uri = request.getRequestURI();
        if (!uri.contains("//")) {
            return uri;
        }
        uri = uri.replaceAll("//+", "/");
        request.setAttribute(attrName, uri);
        return uri;
    }

    public HttpServletRequest getRequest() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }

    public String getRealIp() {
        HttpServletRequest request = getRequest();
        //X-Forwarded-For：Squid 服务代理
        String ipAddresses = request.getHeader("X-Forwarded-For");
        String ip = "";

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //Proxy-Client-IP：apache 服务代理
            ipAddresses = request.getHeader("Proxy-Client-IP");
        }

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //WL-Proxy-Client-IP：weblogic 服务代理
            ipAddresses = request.getHeader("WL-Proxy-Client-IP");
        }

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //HTTP_CLIENT_IP：有些代理服务器
            ipAddresses = request.getHeader("HTTP_CLIENT_IP");
        }

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //X-Real-IP：nginx服务代理
            ipAddresses = request.getHeader("X-Real-IP");
        }

        //有些网络通过多层代理，那么获取到的ip就会有多个，一般都是通过逗号（,）分割开来，并且第一个ip为客户端的真实IP
        if (ipAddresses != null && ipAddresses.length() != 0) {
            ip = ipAddresses.split(",")[0];
        }

        //还是不能获取到，最后再通过request.getRemoteAddr();获取
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    private void printInfo(JoinPoint point, Object body) {
        if (log.isInfoEnabled()) {
            try {
                HttpServletRequest request = getRequest();
                String reqUri = request.getRequestURI();
                log.info("请求已处理: {}", reqUri);
                if(reqUri.equals("/login")){
                    return;
                }
                Long timeStart = (Long) request.getAttribute(TIMER_START);
                String startTime = DateUtil.date(timeStart).toString();
                long timer = timeStart == null ? -1 : System.currentTimeMillis() - timeStart;
                //获取登录信息
                LoginUser loginUser = SecurityUtils.getLoginUser();
                String targetMethod = point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName();
                // debug 级别下打印日志信息
                if (log.isInfoEnabled()) {
                    String timerMsg = timer < 0 ? "未记录" : (timer + " 毫秒");
                    Object data = (body instanceof AjaxResult || body instanceof TableDataInfo) ? JSONUtil.toJsonStr(body) : body;
                    String queryStringDesc = "";
                    String queryString = request.getQueryString();
                    if (StrUtil.isNotEmpty(queryString)) {
                        queryStringDesc = "\r\n查询字符 -->: " + queryString;
                    }
                    String msg = StrUtil.format(
                            "\r\n{}{}\r\n{}{}\r\n{}{}\r\n{}{}{}{}\r\n{}{}\r\n{}{}{}\r\n{}{}\r\n{}{}\r\n{}{}\r\n{}{}\r\n{}{}\r\n{}\r\n",
                            "源ip -->: ", getRealIp(),
                            "请求服务 -->: ", serviceName,
                            "请求路径 -->: ", reqUri,
                            "请求类型 -->: ", request.getMethod(), "    content-type -->: ", request.getContentType(),
                            "目标方法 -->: ", targetMethod,
                            "当前用户 -->: ", JSONUtil.toJsonStr(loginUser.getUser()),
                            queryStringDesc,
                            "绑定参数 -->: ", getRequestValue(point, request.getMethod()),//JSONUtil.toJsonStr(point.getArgs()),
                            "响应参数 -->: ", data,
                            "请求时间 -->: ", startTime,
                            "响应码 -->: ", "200",
                            "服务响应时间 -->: ", timerMsg,
                            "打印结束 -->: --------------------------------------------------------------------------------"
                    );
                    log.info(msg);
                }
            } catch (Exception e) {
                log.error("调试日志发生异常", e);
            }
        }
    }

    @AfterReturning(returning = "body", pointcut = "controllerPointCut()")
    public void after(JoinPoint point, Object body) {
        printInfo(point, body);
    }


    private String getRequestValue(JoinPoint joinPoint, String method) throws Exception {
        Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
        if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(method, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) {
            return argsArrayToString(joinPoint.getArgs());
        } else {
            return JSON.toJSONString(paramsMap);
        }
    }


    private String argsArrayToString(Object[] paramsArray) {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (StringUtils.isNotNull(o)) {
                    try {
                        String jsonObj = JSON.toJSONString(o);
                        params += jsonObj.toString() + " ";
                    } catch (Exception e) {
                    }
                }
            }
        }
        return params.trim();
    }
}
